#!/usr/bin/env python3
#
# Copyright (C) 2018 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
#

import sys

from vyos.configtree import ConfigTree


if len(sys.argv) < 2:
    print("Must specify file name!")
    sys.exit(1)

file_name = sys.argv[1]

with open(file_name, 'r') as f:
    config_file = f.read()

config = ConfigTree(config_file)

def migrate_neighbor(config, neighbor_path, neighbor):
    if config.exists(neighbor_path):
        neighbors = config.list_nodes(neighbor_path)
        for neighbor in neighbors:
            # Move the valueless options: as-override, next-hop-self, route-reflector-client, route-server-client,
            # remove-private-as
            for valueless_option in ['as-override', 'nexthop-self', 'route-reflector-client', 'route-server-client',
                                     'remove-private-as']:
                if config.exists(neighbor_path + [neighbor, valueless_option]):
                    config.set(neighbor_path + [neighbor] + af_path + [valueless_option])
                    config.delete(neighbor_path + [neighbor, valueless_option])

            # Move filter options: distribute-list, filter-list, prefix-list, and route-map
            # They share the same syntax inside so we can group them
            for filter_type in ['distribute-list', 'filter-list', 'prefix-list', 'route-map']:
                if config.exists(neighbor_path + [neighbor, filter_type]):
                    for filter_dir in ['import', 'export']:
                        if config.exists(neighbor_path + [neighbor, filter_type, filter_dir]):
                            filter_name = config.return_value(neighbor_path + [neighbor, filter_type, filter_dir])
                            config.set(neighbor_path + [neighbor] + af_path + [filter_type, filter_dir], value=filter_name)
                    config.delete(neighbor_path + [neighbor, filter_type])

            # Move simple leaf node options: maximum-prefix, unsuppress-map, weight
            for leaf_option in ['maximum-prefix', 'unsuppress-map', 'weight']:
                if config.exists(neighbor_path + [neighbor, leaf_option]):
                    if config.exists(neighbor_path + [neighbor, leaf_option]):
                        leaf_opt_value = config.return_value(neighbor_path + [neighbor, leaf_option])
                        config.set(neighbor_path + [neighbor] + af_path + [leaf_option], value=leaf_opt_value)
                        config.delete(neighbor_path + [neighbor, leaf_option])

            # The rest is special cases, for better or worse

            # Move allowas-in
            if config.exists(neighbor_path + [neighbor, 'allowas-in']):
                if config.exists(neighbor_path + [neighbor, 'allowas-in', 'number']):
                    allowas_in = config.return_value(neighbor_path + [neighbor, 'allowas-in', 'number'])
                    config.set(neighbor_path + [neighbor] + af_path + ['allowas-in', 'number'], value=allowas_in)
                config.delete(neighbor_path + [neighbor, 'allowas-in'])

            # Move attribute-unchanged options
            if config.exists(neighbor_path + [neighbor, 'attribute-unchanged']):
                for attr in ['as-path', 'med', 'next-hop']:
                    if config.exists(neighbor_path + [neighbor, 'attribute-unchanged', attr]):
                        config.set(neighbor_path + [neighbor] + af_path + ['attribute-unchanged', attr])
                        config.delete(neighbor_path + [neighbor, 'attribute-unchanged', attr])
                config.delete(neighbor_path + [neighbor, 'attribute-unchanged'])

            # Move capability options
            if config.exists(neighbor_path + [neighbor, 'capability']):
                # "capability dynamic" is a peer-global option, we only migrate ORF
                if config.exists(neighbor_path + [neighbor, 'capability', 'orf']):
                    if config.exists(neighbor_path + [neighbor, 'capability', 'orf', 'prefix-list']):
                        for orf in ['send', 'receive']:
                            if config.exists(neighbor_path + [neighbor, 'capability', 'orf', 'prefix-list', orf]):
                                config.set(neighbor_path + [neighbor] + af_path + ['capability', 'orf', 'prefix-list', orf])
                                config.delete(neighbor_path + [neighbor, 'capability', 'orf', 'prefix-list', orf])
                        config.delete(neighbor_path + [neighbor, 'capability', 'orf', 'prefix-list'])
                    config.delete(neighbor_path + [neighbor, 'capability', 'orf'])

            # Move default-originate
            if config.exists(neighbor_path + [neighbor, 'default-originate']):
                if config.exists(neighbor_path + [neighbor, 'default-originate', 'route-map']):
                    route_map = config.return_value(neighbor_path + [neighbor, 'default-originate', 'route-map'])
                    config.set(neighbor_path + [neighbor] + af_path + ['default-originate', 'route-map'], value=route_map)
                else:
                    # Empty default-originate node is meaningful so we re-create it
                    config.set(neighbor_path + [neighbor] + af_path + ['default-originate'])
                config.delete(neighbor_path + [neighbor, 'default-originate'])

            # Move soft-reconfiguration
            if config.exists(neighbor_path + [neighbor, 'soft-reconfiguration']):
                if config.exists(neighbor_path + [neighbor, 'soft-reconfiguration', 'inbound']):
                    config.set(neighbor_path + [neighbor] + af_path + ['soft-reconfiguration', 'inbound'])
                # Empty soft-reconfiguration is meaningless, so we just remove it
                config.delete(neighbor_path + [neighbor, 'soft-reconfiguration'])

            # Move disable-send-community
            if config.exists(neighbor_path + [neighbor, 'disable-send-community']):
                for comm_type in ['standard', 'extended']:
                    if config.exists(neighbor_path + [neighbor, 'disable-send-community', comm_type]):
                        config.set(neighbor_path + [neighbor] + af_path + ['disable-send-community', comm_type])
                        config.delete(neighbor_path + [neighbor, 'disable-send-community', comm_type])
                config.delete(neighbor_path + [neighbor, 'disable-send-community'])


if not config.exists(['protocols', 'bgp']):
    # Nothing to do
    sys.exit(0)
else:
    # Just to avoid writing it so many times
    af_path = ['address-family', 'ipv4-unicast']

    # Check if BGP is actually configured and obtain the ASN
    asn_list = config.list_nodes(['protocols', 'bgp'])
    if asn_list:
        # There's always just one BGP node, if any
        asn = asn_list[0]
        bgp_path = ['protocols', 'bgp', asn]
    else:
        # There's actually no BGP, just its empty shell
        sys.exit(0)

    ## Move global IPv4-specific BGP options to "address-family ipv4-unicast"

    # Move networks
    network_path = ['protocols', 'bgp', asn, 'network']
    if config.exists(network_path):
        config.set(bgp_path + af_path + ['network'])
        config.set_tag(bgp_path + af_path + ['network'])

        networks = config.list_nodes(network_path)
        for network in networks:
            config.set(bgp_path + af_path + ['network', network])
            if config.exists(network_path + [network, 'route-map']):
                route_map = config.return_value(network_path + [network, 'route-map'])
                config.set(bgp_path + af_path + ['network', network, 'route-map'], value=route_map)
        config.delete(network_path)

    # Move aggregate-address statements
    aggregate_path = ['protocols', 'bgp', asn, 'aggregate-address']
    if config.exists(aggregate_path):
        config.set(bgp_path + af_path + ['aggregate-address'])
        config.set_tag(bgp_path + af_path + ['aggregate-address'])

        aggregates = config.list_nodes(aggregate_path)
        for aggregate in aggregates:
            config.set(bgp_path + af_path + ['aggregate-address', aggregate])
            if config.exists(aggregate_path + [aggregate, 'as-set']):
                config.set(bgp_path + af_path + ['aggregate-address', aggregate, 'as-set'])
            if config.exists(aggregate_path + [aggregate, 'summary-only']):
                config.set(bgp_path + af_path + ['aggregate-address', aggregate, 'summary-only'])
        config.delete(aggregate_path)

    ## Migrate neighbor options
    neighbor_path = ['protocols', 'bgp', asn, 'neighbor']
    if config.exists(neighbor_path):
        neighbors = config.list_nodes(neighbor_path)
        for neighbor in neighbors:
            migrate_neighbor(config, neighbor_path, neighbor)

    peer_group_path = ['protocols', 'bgp', asn, 'peer-group']
    if config.exists(peer_group_path):
        peer_groups = config.list_nodes(peer_group_path)
        for peer_group in peer_groups:
            migrate_neighbor(config, peer_group_path, peer_group)

    ## Migrate redistribute statements
    redistribute_path = ['protocols', 'bgp', asn, 'redistribute']
    if config.exists(redistribute_path):
        config.set(bgp_path + af_path + ['redistribute'])

        redistributes = config.list_nodes(redistribute_path)
        for redistribute in redistributes:
            config.set(bgp_path + af_path + ['redistribute', redistribute])
            if config.exists(redistribute_path + [redistribute, 'metric']):
                redist_metric = config.return_value(redistribute_path + [redistribute, 'metric'])
                config.set(bgp_path + af_path + ['redistribute', redistribute, 'metric'], value=redist_metric)
            if config.exists(redistribute_path + [redistribute, 'route-map']):
                redist_route_map = config.return_value(redistribute_path + [redistribute, 'route-map'])
                config.set(bgp_path + af_path + ['redistribute', redistribute, 'route-map'], value=redist_route_map)

        config.delete(redistribute_path)

    try:
        with open(file_name, 'w') as f:
            f.write(config.to_string())
    except OSError as e:
        print("Failed to save the modified config: {}".format(e))
        sys.exit(1)
